/**
 * \file
 */
#include "precompiled.hpp"
#include "kfs_mkfs.hpp"
#include "kfs.hpp"
#include "kfs_cache.hpp"
#include "kfs_file_descriptor.hpp"
#include "kfs_impl.hpp"
#include "kfs_time.hpp"
#include "Exception.hpp"
#include <boost/scoped_array.hpp>
#include <fcntl.h>
#include <algorithm>                    // reverse
#include <cstring>
#include <fstream>

namespace po = boost::program_options;
using std::cout;  using std::endl;
using std::ios;  using std::ofstream;  using std::fstream;
using std::auto_ptr;  using std::string;
using std::vector;

static std::streampos
computeDeviceNumBytes(kfs::Impl& impl);


void kfs_mkfs(po::variables_map& params)
{
    uint64_t const timeNowNsec = KfsTime::timeNow();
    string const& deviceName = params["device"].as<string>();
    uint32_t const& blockSize = params["block-size"].as<uint32_t>();

    if(ones32(blockSize) != 1)
    {
        throw Exception("block-size must be a power of two");
    }
    if(blockSize < 128 || blockSize > 65536)
    {
        throw Exception("block-size must be at least 128 and at most 65536");
    }

    if(params.count("num-blocks"))
    {
        uint64_t numBytes = blockSize * params["num-blocks"].as<uint32_t>();
        ofstream f(deviceName.c_str(), ios::binary | ios::trunc);
        f.seekp(numBytes - 1, ios::beg);
        f.put('\0');
        if(!f)
        {
            throw Exception("Could not open device %s", deviceName.c_str());
        }
    }

    {
    kfs::Impl impl;
    impl.f.open(deviceName.c_str(),
        ios::binary | ios::in | ios::out);
    if(!impl.f)
    {
        throw Exception("Error: could not open device %s", deviceName.c_str());
    }
    impl.f.exceptions(ios::badbit | ios::failbit | ios::eofbit);
    impl.mb.blockNumBytes = blockSize;
    impl.mb.numBlocks = computeDeviceNumBytes(impl) / impl.mb.blockNumBytes;
    if(impl.mb.numBlocks < 8)
    {
        throw Exception("%s: error: block device must have at least %u"
            " blocks to create a kfs filesystem", 8);
    }
    boost::scoped_array<uint8_t> block(impl.createBlockBuffer());

    // Write the master block
    impl.serialize(impl.mb, block.get());
    kfs_disk_write(impl, 0, block.get());

    // Write the allocator bitmap.
    {
        memset(block.get(), 0, impl.mb.blockNumBytes);
        block[0] = 1 | 2;
        kfs_disk_write(impl, impl.bitmapDiskBlockNum, block.get());
    }
    // Write the root directory inode.
    impl.rootBlockNum = impl.disk_allocate();
    Inode inode;
    inode.sizeNumBytes = 0;
    inode.userID = 0;
    inode.groupID = 0;
    inode.mode = S_IFDIR | 0755;
    inode.ctime = timeNowNsec;
    inode.mtime = timeNowNsec;
    inode.atime = timeNowNsec;
    inode.linkCount = 0;
    inode.dataBlockAddresses.resize(15, 0);
    impl.serialize(inode, block.get());
    kfs_disk_write(impl, impl.rootBlockNum, block.get());
    printf("Root inode placed at block %u\n", impl.rootBlockNum);
    }

    kfs fs(deviceName);
    boost::scoped_ptr<FileDescriptor> file(
        fs.file_open("/", O_RDWR));
    // Write the entries is the root directory.
    fs.impl->dir_add(file.get(), ".", file->inodeRef->elt->second->blockNum);
}


// This belongs to kfs_init().
std::streampos
computeDeviceNumBytes(kfs::Impl& impl)
{
    impl.f.clear();
    impl.f.exceptions(ios::badbit | ios::failbit | ios::eofbit);
    impl.f.seekg(0, ios::beg);
    std::streampos fsize = impl.f.tellg();
    impl.f.seekg(0, ios::end);
    fsize = impl.f.tellg() - fsize;
    return fsize;
}
